package com.mikhailenko.transportMVC.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.mikhailenko.transportMVC.model.Criteria;
import com.mikhailenko.transportMVC.model.Firm;
import com.mikhailenko.transportMVC.model.Group;
import com.mikhailenko.transportMVC.model.es.Block;
import com.mikhailenko.transportMVC.model.es.BlockEntity;
import com.mikhailenko.transportMVC.model.es.CalculateForm;
import com.mikhailenko.transportMVC.model.es.ES;
import com.mikhailenko.transportMVC.model.es.ESEntity;
import com.mikhailenko.transportMVC.model.es.InputEntity;
import com.mikhailenko.transportMVC.model.es.InputEntityFake;
import com.mikhailenko.transportMVC.model.es.LayerEntity;
import com.mikhailenko.transportMVC.model.es.ReadyES;
import com.mikhailenko.transportMVC.model.es.RuleDB;
import com.mikhailenko.transportMVC.model.es.TermEnum;
import com.mikhailenko.transportMVC.service.es.BlockService;
import com.mikhailenko.transportMVC.service.es.ESService;
import com.mikhailenko.transportMVC.service.es.InputFakeService;
import com.mikhailenko.transportMVC.service.es.InputService;
import com.mikhailenko.transportMVC.service.es.LayerService;
import com.mikhailenko.transportMVC.service.es.ReadyESService;
import com.mikhailenko.transportMVC.service.es.RuleService;
import com.mikhailenko.transportMVC.utils.Convertor;

@Controller
@RequestMapping(value="/es")
public class EsController {

    
    @Autowired
    private ESService esService;
    
    @Autowired
    private LayerService layerService;
    
    @Autowired
    private BlockService blockService;
    
    @Autowired
    private InputFakeService inputFakeService;
    
    @Autowired
    private InputService inputService;
    
    @Autowired
    private RuleService ruleService;
    
    @Autowired
    private ReadyESService readyEsService;
    
    @RequestMapping(value="/layers", method=RequestMethod.GET)
    public ModelAndView getCountOfLayersCreate(){
        ModelAndView mav = new ModelAndView("es/get-layers", "es", new ES());
        
        return mav;
    }
    
    @RequestMapping(value="/layers", method=RequestMethod.POST)
    public ModelAndView getCountOfLayers(@ModelAttribute ESEntity es,
            final RedirectAttributes redirectAttributes){
        ModelAndView mav = new ModelAndView();
        
        ESEntity createdES = esService.create(es);
        
        redirectAttributes.addFlashAttribute("esId", createdES.getId());
        mav.setViewName("redirect:/es/blocks");
        return mav;
    }
    
    @RequestMapping(value="/blocks", method=RequestMethod.GET)
    public ModelAndView getLayerInformation(@ModelAttribute("esId") final int id){
        
        ESEntity findedES = esService.findById(id);
        
        ES es = new ES();
        es.setCount(findedES.getCountOfLayers());
        
        ModelAndView mav = new ModelAndView("es/get-blocks", "es", es);
        return mav;
    }
    
    @RequestMapping(value="/blocks", method=RequestMethod.POST)
    public ModelAndView getLayerInformationSend(@ModelAttribute("es") ES es,
            @ModelAttribute("esId") final int id,
            final RedirectAttributes redirectAttributes){

        ModelAndView mav= new ModelAndView();
        mav.setViewName("redirect:/es/blocks-inputs");
        
        es.setCountOfBloks(Convertor.listToList(es.getCountOfBloksString()));
        es.setCount(es.getCountOfBloksString().size());
        
        ESEntity findedES = esService.findById(id);
        
        redirectAttributes.addFlashAttribute("layersId", saveLayers(es, findedES));
        redirectAttributes.addFlashAttribute("esId", id);
        return mav;
    }
    
    
    @RequestMapping(value="/blocks-inputs", method=RequestMethod.GET)
    public ModelAndView getBlocksRules(@ModelAttribute("esId") final int id,
            @ModelAttribute("layersId") final String layersId){

        ESEntity findedES = esService.findById(id);
        
        int[] layersIdMas = getEntetyId(layersId);
        
        ES es = new ES();
        es.setCount(findedES.getCountOfLayers());
        List<Integer> countOfBlocks = new ArrayList<Integer>();
        
        for (int layerId: layersIdMas){
            LayerEntity layer = layerService.findById(layerId);
            countOfBlocks.add(layer.getCountOfBlocks());
        }
        es.setCountOfBloks(countOfBlocks);
        
        ModelAndView mav = new ModelAndView("es/get-blocks-inputs", "es", es);
        
        return mav;
    }
    
    @RequestMapping(value="/blocks-inputs", method=RequestMethod.POST)
    public ModelAndView getBloksInputs(@ModelAttribute("es") ES es,
            @ModelAttribute("esId") final int id,
            @ModelAttribute("layersId") final String layersId,
            RedirectAttributes redirectAttributes){
        
        ModelAndView mav = new ModelAndView();
        mav.setViewName("redirect:/es/blocks-inputs-name");
        
        int[] layersIdMas = getEntetyId(layersId);
        
        ESEntity findedES = esService.findById(id);
        es.setCount(findedES.getCountOfLayers());
        
        StringBuilder blocksId = new StringBuilder();
        int index = 0;
        for (int i = 0; i < es.getCount(); i++){
            LayerEntity layer = layerService.findById(layersIdMas[i]);
            for (int j = 0; j < layer.getCountOfBlocks(); j++){
                BlockEntity block = new BlockEntity();
                block.setName(es.getBlocks().get(index).getName());
                block.setCountOfInputs(es.getBlocks().get(index).getCountOfInputs());
                block.setLayer(layer);
                BlockEntity createdBlock = blockService.create(block);
                blocksId.append(createdBlock.getId());
                blocksId.append(";");
                index++;
            }
        }
        blocksId.deleteCharAt(blocksId.lastIndexOf(";"));
        
        redirectAttributes.addFlashAttribute("esId", id);
        redirectAttributes.addFlashAttribute("layersId", layersId);
        redirectAttributes.addFlashAttribute("blocksId", blocksId);
        //TODO: must be done
        return mav;
    }
    
    @RequestMapping(value="/blocks-inputs-name", method=RequestMethod.GET)
    public ModelAndView getBlocksInputsNames( @ModelAttribute("esId") final int id,
            @ModelAttribute("layersId") final String layersId,
            @ModelAttribute("blocksId") final String blocksId){
        //TODO: get es from DB
//        TODO: should check, should be not ready
        
        ESEntity findedES = esService.findById(id);
        ES es = new ES();
        es.setCount(findedES.getCountOfLayers());
        
        int[] layersIdMas = getEntetyId(layersId);
        List<Integer> countOfBlocks = new ArrayList<Integer>();
        
        for (int layerId: layersIdMas){
            LayerEntity layer = layerService.findById(layerId);
            countOfBlocks.add(layer.getCountOfBlocks());
        }
        es.setCountOfBloks(countOfBlocks);
        
        List<InputEntityFake> inputsAll = inputFakeService.findAll();
        ArrayList<String> inputsList = new ArrayList<String>();
        for (int i = 0; i < inputsAll.size(); i++){
            inputsList.add(inputsAll.get(i).getName());
        } 

        List<InputEntityFake> outputs = inputFakeService.findByType(false);
        ArrayList<String> outputsList = new ArrayList<String>();
        for (int i = 0; i < outputs.size(); i++){
            outputsList.add(outputs.get(i).getName());
        } 
        
        List<Block> blocksFake = new ArrayList<Block>();
        int[] blocksIdMas = getEntetyId(blocksId);
        
        List<BlockEntity> blocksEntety = new ArrayList<BlockEntity>();
        for (int i = 0; i < blocksIdMas.length; i++){
            blocksEntety.add(blockService.findById(blocksIdMas[i]));
        }
        
        for (BlockEntity block: blocksEntety){
            Block block1 = new Block();
            block1.setName(block.getName());
            block1.setCountOfInputs(block.getCountOfInputs());
            blocksFake.add(block1);
        }
        es.setBlocks(blocksFake);
        ModelAndView mav = new ModelAndView("es/get-blocks-input-name", "es", es);
        mav.addObject("inputsList", inputsList);
        mav.addObject("outputsList", outputsList);
        
        
        return mav;
    }
    
    @RequestMapping(value="/blocks-inputs-name", method=RequestMethod.POST)
    public ModelAndView getBlocksInputsNamesSend(@ModelAttribute("es") ES es,
    		@ModelAttribute("esId") final int id,
            @ModelAttribute("layersId") final String layersId,
            @ModelAttribute("blocksId") final String blocksId,
    		RedirectAttributes redirectAttributes){

        //TODO: save es to DB
    	StringBuilder inputsId = new StringBuilder();
    	for (Block block: es.getBlocks()){
    		BlockEntity findedBlock = blockService.findByName(block.getName());
    		for (String name: block.getInputs()){
    			InputEntityFake donor = inputFakeService.findByName(name);
    			InputEntity input = new InputEntity();
    			input.setName(name);
    			input.setUsed(true);
    			input.setType(donor.isType());
    			input.setBlock(findedBlock);
    			InputEntity createdInput = inputService.create(input);
    			inputsId.append(createdInput.getId());
    			inputsId.append(";");
    		}
    		InputEntity output = new InputEntity();
    		output.setName(block.getOutput());
    		output.setType(false);
    		output.setUsed(true);
    		output.setBlock(findedBlock);
    		InputEntity createdOutput = inputService.create(output);
    		inputsId.append(createdOutput.getId());
    		inputsId.append(";");
    	}
       
    	inputsId.deleteCharAt(inputsId.lastIndexOf(";"));
    	
        ModelAndView mav = new ModelAndView();
        mav.setViewName("redirect:/es/rules");
        
        redirectAttributes.addFlashAttribute("esId", id);
        redirectAttributes.addFlashAttribute("layersId", layersId);
        redirectAttributes.addFlashAttribute("blocksId", blocksId);
        redirectAttributes.addFlashAttribute("inputsId", inputsId);
        return mav;
    }
    
    @RequestMapping(value="/rules", method = RequestMethod.GET)
    public ModelAndView RulesPageCreate( @ModelAttribute("esId") final int id,
            @ModelAttribute("layersId") final String layersId,
            @ModelAttribute("blocksId") final String blocksId,
            @ModelAttribute("inputsId") final String inputsId){
    	
    	ESEntity findedES = esService.findById(id);
    	
        ES es = new ES();
        es.setCount(findedES.getCountOfLayers());
        
        int[] layersIdMas = getEntetyId(layersId);
        List<Integer> countOfBlocks = new ArrayList<Integer>();
        
        for (int layerId: layersIdMas){
            LayerEntity layer = layerService.findById(layerId);
            countOfBlocks.add(layer.getCountOfBlocks());
        }
        es.setCountOfBloks(countOfBlocks);

        List<Block> blocksFake = new ArrayList<Block>();
        int[] blocksIdMas = getEntetyId(blocksId);
        
        List<BlockEntity> blocksEntety = new ArrayList<BlockEntity>();
        for (int i = 0; i < blocksIdMas.length; i++){
            blocksEntety.add(blockService.findById(blocksIdMas[i]));
        }
        
        int[] inputsIdMas = getEntetyId(inputsId);
        
    	int index = 0;
        for (BlockEntity block: blocksEntety){
            Block block1 = new Block();
            block1.setName(block.getName());
            block1.setCountOfInputs(block.getCountOfInputs());
            List<String> inputsName = new ArrayList<String>();
            for (int i = 0; i < block.getCountOfInputs(); i++){
            	InputEntity inputEntity = inputService.findById(inputsIdMas[index]);
            	inputsName.add(inputEntity.getName());
            	index++;
            }
            block1.setInputs(inputsName);
            
            InputEntity outputEntity = inputService.findById(inputsIdMas[index]);
        	index++;
        	block1.setOutput(outputEntity.getName());
            
            blocksFake.add(block1);
        }
        es.setBlocks(blocksFake);
        

        ModelAndView mav = new ModelAndView("es/get-rules", "es", es);
        mav.addObject("terms", TermEnum.values());
        return mav;
    }
    
    @RequestMapping(value="/rules", method = RequestMethod.POST)
    public ModelAndView RulesPageSave(@ModelAttribute("es") ES es,
    		 @ModelAttribute("esId") final int id,
             @ModelAttribute("layersId") final String layersId,
             @ModelAttribute("blocksId") final String blocksId,
             RedirectAttributes redirectAttributes){
    	
    	ModelAndView mav = new ModelAndView();
    	mav.setViewName("redirect:/");
    	
		int[] blocksIdMas = getEntetyId(blocksId);
    	
    	List<BlockEntity> blocksEntety = new ArrayList<BlockEntity>();
		for (int i = 0; i < blocksIdMas.length; i++){
		    blocksEntety.add(blockService.findById(blocksIdMas[i]));
		}
    	
    	for (int i = 0; i < es.getBlocks().size(); i++){
    		List<List<String>> rules = es.getBlocks().get(i).getRules();
    		for (List<String> rule: rules){
    			StringBuilder ruleStr = new StringBuilder();
    			for (int j = 0; j < rule.size() - 1; j++){
    				ruleStr.append(rule.get(j));
    				ruleStr.append(";");
    			}
    			ruleStr.deleteCharAt(ruleStr.lastIndexOf(";"));
        		RuleDB ruleDb = new RuleDB();
        		ruleDb.setInputs(ruleStr.toString());
        		ruleDb.setResult(rule.get(rule.size() - 1));
        		ruleDb.setBlock(blocksEntety.get(i));
        		ruleService.create(ruleDb);
    		}
    	}
    	
    	ReadyES readyEs = new ReadyES();
    	readyEs.setEsId(id);
    	readyEsService.create(readyEs);
    	
    	String message = "Expert system structure create successfully";
    	redirectAttributes.addFlashAttribute("message", message);
    	return mav;
    }
    
    @RequestMapping(value = "/calculate", method = RequestMethod.GET)
    public ModelAndView calculateResultCreate(){

    	List<ReadyES> readyEsList = readyEsService.findAll();
    	ReadyES readyES = readyEsList.get(readyEsList.size() - 1);
    	
    	ESEntity findedEs = esService.findById(readyES.getEsId());
    	
    	List<LayerEntity> layers = layerService.findByEsId(findedEs.getId());
    	
    	List<BlockEntity> blocks = new ArrayList<BlockEntity>();
    	
    	for (LayerEntity layer: layers){
    		List<BlockEntity> blocksByLayers = blockService.findByLayerId(layer.getId());
    		blocks.addAll(blocksByLayers);
    	}
    	
    	List<InputEntity> inputsAll = new ArrayList<InputEntity>();
    	for (BlockEntity block: blocks){
    		List<InputEntity> inputsByBlocks = inputService.findByBlockId(block.getId());
    		inputsAll.addAll(inputsByBlocks);
    	}
    	
    	List<InputEntity> inputs = new ArrayList<InputEntity>();
    	for (InputEntity input: inputsAll){
    		if (input.isType()){
    			inputs.add(input);
    		}
    	}
    	
    	CalculateForm calculate = new CalculateForm();
    	calculate.setInputs(inputs);
    	
    	ModelAndView mav = new ModelAndView("es/calculate", "calculate", calculate);
    	
    	mav.addObject("inputs", inputs);
    	mav.addObject("terms", TermEnum.values());
    	return mav;
    }
    
    @RequestMapping(value = "/calculate", method = RequestMethod.POST)
    public ModelAndView calculateResult(@ModelAttribute("calculate") CalculateForm calculate,
    		RedirectAttributes redirectAttributes){

    	List<ReadyES> readyEsList = readyEsService.findAll();
    	ReadyES readyES = readyEsList.get(readyEsList.size() - 1);
    	
    	ESEntity findedEs = esService.findById(readyES.getEsId());
    	
    	List<LayerEntity> layers = layerService.findByEsId(findedEs.getId());
    	
    	List<BlockEntity> blocks = new ArrayList<BlockEntity>();
    	
    	for (LayerEntity layer: layers){
    		List<BlockEntity> blocksByLayers = blockService.findByLayerId(layer.getId());
    		blocks.addAll(blocksByLayers);
    	}
    	
    	List<InputEntity> inputsAll = new ArrayList<InputEntity>();
    	for (BlockEntity block: blocks){
    		List<InputEntity> inputsByBlocks = inputService.findByBlockId(block.getId());
    		inputsAll.addAll(inputsByBlocks);
    	}
    	
    	
    	List<InputEntity> inputs = new ArrayList<InputEntity>();
    	for (InputEntity input: inputsAll){
    		if (input.isType()){
    			inputs.add(input);
    		}
    	}
    	
    	Map<String, String> globalInputs = new HashMap<String, String>();
    	for (int i = 0; i < inputs.size(); i++){
    		globalInputs.put(inputs.get(i).getName(), calculate.getInputs().get(i).getTerm().toString());
    	}
    	
    	for (BlockEntity block: blocks){
    		List<InputEntity> inputsByBlock = inputService.findByBlockId(block.getId());
    		List<RuleDB> rulesByBlock = ruleService.findByBlockId(block.getId());
    		
    		for (RuleDB rule: rulesByBlock){
    			String[] terms = rule.getInputs().split(";");
    			int i = 0;
    			boolean status = false;
    			for (String term: terms){
    				String inputValue = globalInputs.get(inputsByBlock.get(i).getName());
    				//TODO: fuzzyfication
    				if (term.equals(inputValue)){
    					status = true;
    				} else {
    					status = false;
    					break;
    				}
    				i++;
    			}
    			if (status){
    				globalInputs.put(inputsByBlock.get(inputsByBlock.size() - 1).getName(), rule.getResult());
    				break;
    			}
    		}
    	}
    	
    	
    	
    	
    	ModelAndView mav = new ModelAndView();
    	mav.setViewName("redirect:/es/calculate");
    	redirectAttributes.addFlashAttribute("message", globalInputs.get(globalInputs.keySet().toArray()[globalInputs.size() - 1]));
    	
    	return mav;
    }
    
    @RequestMapping(value="/hierarchy", method=RequestMethod.GET)
    public ModelAndView newHierarchyPage(){
        ModelAndView mav = new ModelAndView("/es/es_structure");
        
        return mav;
    }
    
    @ModelAttribute("es")
    public ES getEs() {
        return new ES();
    }
    
    private String saveLayers(ES es, ESEntity findedES){
        StringBuilder layersId = new StringBuilder();
        
        for (int i = 0; i < es.getCount(); i++){
            LayerEntity layer = new LayerEntity();
            layer.setCountOfBlocks(es.getCountOfBloks().get(i));
            layer.setEs(findedES);
            LayerEntity createdLayer = layerService.create(layer);
            layersId.append(createdLayer.getId());
            layersId.append(";");
        } 
        
        layersId.deleteCharAt(layersId.lastIndexOf(";"));
        
        return layersId.toString();
    }
    
    private int[] getEntetyId(String entetyId){
        String[] temp = entetyId.split(";");
        int[] result = new int[temp.length];
        
        for (int i = 0; i < temp.length; i++){
            result[i] = Integer.parseInt(temp[i]);
        }
        
        return result;
    }
}
